/**
 * Copyright (c) 2010 by Kevin Bierhoff.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *    3. Neither the names of the authors nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.syper.javasyp.formulae;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeLiteral;

/**
 * TODO make all literals names so we can call methods on them
 * @author kevin
 * @since Jan 30, 2010
 *
 */
public class Literal implements Formula {

	public static final Formula TRUE = new Literal(Boolean.TRUE);
	public static final Formula FALSE = new Literal(Boolean.FALSE);
	public static final Formula NULL = new NullLiteral();
	public static final Formula ZERO = new Literal(0);
	public static final Formula DZERO = new Literal(0.0);

	private static final Map<String, Formula> PRIM_DFLT_INITIALIZERS;

	static {
		Map<String, Formula> primDfltInit = new HashMap<String, Formula>();
		primDfltInit.put("boolean", Literal.FALSE);
		primDfltInit.put("int", Literal.ZERO);
		primDfltInit.put("long", Literal.ZERO);
		primDfltInit.put("byte", Literal.ZERO);
		primDfltInit.put("short", Literal.ZERO);
		primDfltInit.put("double", DZERO);
		primDfltInit.put("float", DZERO);
		primDfltInit.put("char", new Literal('\0'));
		PRIM_DFLT_INITIALIZERS = Collections.unmodifiableMap(primDfltInit);
	}

	public static Formula getDefaultInitializer(ITypeBinding type) {
		if (! type.isPrimitive())
			return Literal.NULL;
		return PRIM_DFLT_INITIALIZERS.get(type.getName());
	}

	/**
	 * This will not work for enum constants--
	 * resolves XxxLiteral AST nodes to literals
	 * @param constant must be a XxxLiteral AST node to work
	 */
	public static Formula getLiteral(Expression constant) {
		Object value = constant.resolveConstantExpressionValue();
		if (value == null) {
			if (constant instanceof org.eclipse.jdt.core.dom.NullLiteral)
				return NULL;
			else if (constant instanceof TypeLiteral)
				// use type binding as the literal--can't necessarily load the referenced class in Eclipse's VM
				return new Literal(((TypeLiteral) constant).getType().resolveBinding());
			else
				throw new IllegalArgumentException("Not a constant: " + constant);
		}
		return new Literal(value);
	}

	/**
	 * Returns a literal for the given integer.
	 * @param value
	 * @return a literal for the given integer.
	 */
	public static Formula getLiteral(int value) {
		if (value == 0)
			return ZERO;
		else
			return new Literal(Integer.valueOf(value));
	}

	/**
	 * constant expression value of this literal
	 * (includes boxed primitives, class constants, and strings).
	 */
	private final Object value;

	/**
	 * Do not call this constructor for {@code null} unless from {@link NullLiteral}.
	 * Literal for the given constant expression value
	 * (includes boxed primitives, class constants, and strings).
	 * @param value Must be a constant value
	 * @see Field#getFieldLiteral()
	 */
	private Literal(Object value) {
		super();
		if (value == null && ! (this instanceof NullLiteral))
			throw new IllegalArgumentException("Must use NullLiteral for null");
		this.value = value;
	}

	/**
	 * Returns the literal value, boxed for primitives
	 * @return the literal, including {@code null} and {@link ITypeBinding}s for {@code .class} literals
	 */
	public Object getValue() {
		return value;
	}

	@Override
	public <T> T dispatch(FormulaVisitor<T> visitor) {
		return visitor.visit(this);
	}

	@Override
	public boolean isBoolean() {
		return value != null && value instanceof Boolean;
	}

	@Override
	public String toString() {
		return String.valueOf(value);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((value == null) ? 0 : value.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Literal other = (Literal) obj;
		if (value == null) {
			if (other.value != null)
				return false;
		} else if (!value.equals(other.value))
			return false;
		return true;
	}

	/**
	 * Singleton class representing the {@code null} literal.
	 * @author kevin
	 * @since Sep 3, 2010
	 */
	private static class NullLiteral extends Literal implements Name {

		private NullLiteral() {
			super((Object) null);
		}

		@Override
		public ITypeBinding getType() {
			return null;
		}

		@Override
		public String toString() {
			return "Null"; // uppercase to distinguish from null object
		}
	}
}
